package userservice

import (
	"context"
	"fmt"
	"gitee.com/go-mid/infra/xcore"
	"gitee.com/go-mid/infra/xlog"
	"gitee.com/go-mid/infra/xsql/xdb"
	"gitee.com/go-mid/infra/xtime"
	"gitee.com/go-mid/user/internal/component"
	"gitee.com/go-mid/user/internal/dao"
	"gitee.com/go-mid/user/internal/entity"
	"gitee.com/go-mid/user/internal/util"
	"github.com/jinzhu/copier"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"time"
)

//登录认证信息
type AuthInfo struct {
	//用户信息
	UserInfo *UserInfo `json:"user_info"`
	//accesstoken
	AccessToken string `json:"access_token"`
	//accesstoken expire
	AccessTokenExpire int64 `json:"access_token_expire"`
	//refreshtoken
	RefreshToken string `json:"refresh_token"`
	//accesstoken expire
	RefreshTokenExpire int64 `json:"refresh_token_expire"`
} //@name 登录认证信息

type GetUserReq struct {
	UserID  int64 `json:"user_id"`  //  用户表唯一标识
	ActorID int64 `json:"actor_id"` //
} //@name 获取用户表入参

type UserInfo struct {
	ID             int64                    `json:"id" bdb:"id"`                           //
	UserID         int64                    `json:"user_id" bdb:"user_id"`                 //用户id
	Ct             int64                    `json:"ct" bdb:"ct"`                           //创建时间
	Ut             int64                    `json:"ut" bdb:"ut"`                           //修改时间
	State          EnumUserState            `json:"state" bdb:"state"`                     //状态。1正常，2注销
	RegisterStatus EnumRegisterStatus       `json:"register_status" bdb:"register_status"` //状态:0 游客, 1注册用户
	Passwd         string                   `json:"passwd" bdb:"passwd"`                   //密码
	Attrs          map[string]*UserAttrInfo `json:"attrs"`                                 //用户属性
} //@name 用户表信息

type GetUserRes struct {
	UserInfo *UserInfo `json:"user_info"`
} //@name 获取用户表响应

func (m *UserServiceImpl) GetUser(ctx context.Context, req *GetUserReq) (res *GetUserRes, err error) {
	fun := "UserServiceImpl.GetUser -->"
	st := xtime.NewTimeStat()
	defer func() {
		xlog.Infow(ctx, fun, "durationMs", st.Millisecond(), "req", req, "res", res, "err", err)
	}()
	entityUser, err := m.userEntity(ctx, req.UserID, req.ActorID, false)
	if err != nil {
		xlog.Warnf(ctx, "%s userEntity err: req: %v, err: %v", fun, req, err)
		err = status.Error(codes.Internal, err.Error())
		return
	}
	if entityUser == nil {
		return nil, nil
	}
	res = &GetUserRes{
		UserInfo: m.toUserInfo(ctx, entityUser),
	}
	return
}

func (m *UserServiceImpl) toUserInfo(ctx context.Context, item *EntityUser) *UserInfo {
	if item == nil {
		return nil
	}
	var info UserInfo
	copier.Copy(&info, item)
	info.Attrs = make(map[string]*UserAttrInfo)
	for k, v := range item.Attrs {
		if attrInfo := m.toUserAttrInfo(ctx, v); attrInfo != nil {
			info.Attrs[k] = attrInfo
		}
	}
	return &info
}
func (m *UserServiceImpl) toUserInfos(ctx context.Context, items []*EntityUser) []*UserInfo {
	var infos = make([]*UserInfo, 0)
	for _, item := range items {
		infos = append(infos, m.toUserInfo(ctx, item))
	}
	return infos
}

type RegisterReq struct {
	RegisterParam
	IsLogin bool `json:"is_login"` // 是否注册后立即登录
} //@name 注册入参

type RegisterRes struct {
	LoginResult
	//是否新用户
	IsNew bool `json:"is_new"`
} //@name 注册响应

func (m *UserServiceImpl) Register(ctx context.Context, req *RegisterReq) (res *RegisterRes, err error) {
	fun := "UserServiceImpl.Register -->"
	st := xtime.NewTimeStat()
	defer func() {
		xlog.Infow(ctx, fun, "durationMs", st.Millisecond(), "req", req, "res", res, "err", err)
	}()
	userId, err := m.getUserIdByLoginid(ctx, req.LoginID)
	if err != nil {
		xlog.Warnf(ctx, "%s get loginid info error req: %v err: %s", fun, req, err)
		err = status.Error(codes.Internal, err.Error())
		return
	}
	//是否新用户
	isNewUser := userId <= 0
	//新用户执行注册
	if isNewUser {
		userId, err = m.doRegister(ctx, req)
		if err != nil {
			xlog.Warnf(ctx, "%s register error userId: %d req: %v err: %s", fun, userId, req, err)
			err = status.Error(codes.Internal, err.Error())
			return
		}
		//注册后置处理器
		m.postProcessRegister(ctx, userId, req)
	}
	var authInfo *AuthInfo
	if req.IsLogin {
		//执行登录
		authInfo, err = m.login(ctx, &LoginParam{
			LoginID: LoginID{
				LoginidType: req.LoginidType,
				Loginid:     req.Loginid,
				UserPoolID:  req.UserPoolID,
				Appid:       req.Appid,
			},
			UserId:     userId,
			ClientInfo: req.ClientInfo,
			AuthType:   req.AuthType,
		})
	}
	//把刚注册的用户信息读取出来，响应出去
	var userInfo *UserInfo
	if userRes, err := m.GetUser(ctx, &GetUserReq{
		UserID: userId,
	}); err != nil {
		xlog.Warnf(ctx, "%s get user error: req: %v, err: %v", fun, req, err)
	} else {
		userInfo = userRes.UserInfo
	}

	res = &RegisterRes{
		LoginResult: LoginResult{
			AuthInfo: authInfo,
			LoginID: LoginID{
				LoginidType: req.LoginidType,
				Loginid:     req.Loginid,
				UserPoolID:  req.UserPoolID,
				Appid:       req.Appid,
			},
			UserInfo: userInfo,
		},
		IsNew: isNewUser,
	}
	return
}

func formatAttrValue(ctx context.Context, keyType EnumUserAttrType, val string) string {
	switch keyType {
	case EnumUserAttrType_Hash:
		val = hashValue(val)
	case EnumUserAttrType_Encry:
		salt, iv := getEncryKey(ctx)
		val = xcore.Encode(ctx, val, salt, iv)
	}
	return val
}

type UserAttr struct {
	Attr    string           `json:"attr" bdb:"attr"` //属性key
	Val     string           `json:"val" bdb:"val"`   //属性值
	KeyType EnumUserAttrType `json:"key_type"`        // 属性类型
} //@name 用户属性
type UpsertUserAttrsReq struct {
	UserID int64                `json:"user_id" bdb:"user_id"` //用户id
	AppID  int64                `json:"app_id" bdb:"app_id"`   //所属应用
	Attrs  map[string]*UserAttr `json:"attrs"`                 // 变更的属性
} //@name 添加或修改用户属性入参

type UpsertUserAttrsRes struct {
} //@name 添加或修改用户属性响应

func (m *UserServiceImpl) UpsertUserAttr(ctx context.Context, req *UpsertUserAttrsReq) (res *UpsertUserAttrsRes, err error) {
	fun := "UserServiceImpl.UpsertUserAttr -->"
	st := xtime.NewTimeStat()
	defer func() {
		//属性变更，缓存删除
		m.delUserCache(ctx, req.UserID)
		xlog.Infow(ctx, fun, "durationMs", st.Millisecond(), "req", req, "res", res, "err", err)
	}()
	if len(req.Attrs) == 0 {
		xlog.Warnf(ctx, "%s empty attr : req: %v ", fun, req)
		err = fmt.Errorf("empty attr")
		return
	}
	var attrMap = make(map[string]*UserAttr)
	for k, v := range req.Attrs {
		v.Attr = k
		attrMap[k] = v
	}
	var now = time.Now().Unix()
	//按照组件进行全局锁。
	err = util.DistributeLockExec(ctx, fmt.Sprintf("UpsertUserAttr.%d", req.UserID), time.Minute, 30*time.Second, func() error {
		existsAttrs, er := dao.ListUserAttrs(ctx, component.XDBUser, req.UserID)
		if er != nil {
			return er
		}
		xlog.Infof(ctx, "%s before update : %v", fun, existsAttrs)
		for _, exits := range existsAttrs {
			v, ok := attrMap[exits.Attr]
			if !ok || v == nil {
				continue
			}
			//把修改的key排除掉，剩下的都是添加的key
			delete(attrMap, exits.Attr)
			_, err := component.XDBUser.Update(ctx, entity.UserAttrTable, map[string]interface{}{
				"id": exits.ID,
			}, map[string]interface{}{
				"val":      formatAttrValue(ctx, v.KeyType, v.Val),
				"ut":       now,
				"key_type": v.KeyType,
			})
			if err != nil {
				xlog.Warnf(ctx, "%s UpdateUserAttr error: v: %v, err: %v", fun, v, err)
				return err
			}
			//发更新事件
			m.postProcessUpdate(ctx, req.UserID, exits.Val, v)
		}
		var inserts []map[string]interface{}
		for _, v := range attrMap {
			var ups = map[string]interface{}{
				"val":      formatAttrValue(ctx, v.KeyType, v.Val),
				"attr":     v.Attr,
				"user_id":  req.UserID,
				"ct":       now,
				"ut":       now,
				"app_id":   req.AppID,
				"key_type": v.KeyType,
			}
			inserts = append(inserts, ups)
		}
		xlog.Infof(ctx, "%s inserts : %v", fun, inserts)
		if len(inserts) > 0 {
			_, err := component.XDBUser.Insert(ctx, entity.UserAttrTable, inserts)
			if err != nil {
				xlog.Warnf(ctx, "%s InsertUserAttr error: v: %v, err: %v", fun, inserts, err)
				return err
			}
		}
		for _, v := range attrMap {
			//发更新事件
			m.postProcessUpdate(ctx, req.UserID, "", v)
		}
		return nil

	})
	if err != nil {
		xlog.Warnf(ctx, "%s DistributeLockExec err: req: %v, err: %v", fun, req, err)
		return
	}
	res = &UpsertUserAttrsRes{}
	return
}

type BindLoingidReq struct {
	LoginID
	UserId int64
} //@name 绑定登录凭证请求

type BindLoingidRes struct {
	//之前是否绑定过
	HasBind bool `json:"has_bind"`
	//之前绑定过，但是被解绑了，本次操作进行了重新绑定
	IsRebind bool `json:"is_rebind"`
	//登录凭证唯一标识
	Loginid string `json:"loginid"`
} //@name 绑定登录凭证响应

func (m *UserServiceImpl) BindLoingid(ctx context.Context, req *BindLoingidReq) (res *BindLoingidRes, err error) {
	fun := "UserServiceImpl.BindLoingid -->"
	st := xtime.NewTimeStat()
	defer func() {
		xlog.Infow(ctx, fun, "durationMs", st.Millisecond(), "req", req, "res", res, "err", err)
	}()
	if req.UserId <= 0 {
		xlog.Warnf(ctx, "%s invalid param req: %v  ", fun, req)
		err = status.Error(codes.InvalidArgument, "userid invalid")
		return
	}
	oldLoginid, err := m.getLoginid(ctx, req.LoginID)
	if err != nil {
		xlog.Warnf(ctx, "%s get loginid info error req: %v err: %s", fun, req, err)
		err = status.Error(codes.Internal, err.Error())
		return
	}
	if oldLoginid != nil {
		if oldLoginid.State == int32(EnumLoginidState_NORMAL) {
			if oldLoginid.UserID != req.UserId {
				xlog.Warnf(ctx, "%s loginid has bind to another userid error req: %v binduserid: %d ", fun, req, oldLoginid.UserID)
				err = status.Error(codes.Internal, fmt.Sprintf("loginid has bind to another userid: %d", oldLoginid.UserID))
				return
			}
			res = &BindLoingidRes{
				HasBind: true,
				Loginid: oldLoginid.Loginid,
			}
			return
		}
		// 之前存在记录，则把状态修改为正常即可
		_, err = component.XDBUser.Update(ctx, entity.UserLoginidTable, map[string]interface{}{
			"id": oldLoginid.ID,
		}, map[string]interface{}{
			"state": EnumLoginidState_NORMAL,
		})
		if err != nil {
			xlog.Warnf(ctx, "%s Update UserLoginidTable err req: %v err: %s", fun, req, err)
			return
		}
		res = &BindLoingidRes{
			HasBind:  true,
			IsRebind: true,
			Loginid:  req.Loginid,
		}
		return
	}

	now := time.Now().Unix()
	var registerLoginId = &entity.UserLoginid{
		Ct:          now,
		Ut:          now,
		UserID:      req.UserId,
		LoginidType: int32(req.LoginidType),
		Loginid:     m.encryLoginid(ctx, req.LoginID),
		State:       int32(EnumLoginidState_NORMAL),
	}
	loginidUps, err := xdb.BuildDbSqlMap(ctx, registerLoginId, true)
	if err != nil {
		xlog.Warnf(ctx, "%s build loginid ups error: req: %v, err: %v", fun, req, err)
		err = status.Error(codes.Internal, err.Error())
		return
	}
	// 保存账号和登录凭证  绑定关系
	_, err = component.XDBUser.Insert(ctx, entity.UserLoginidTable, []map[string]interface{}{loginidUps})
	if err != nil {
		xlog.Warnf(ctx, "%s insert loginid err req: %v err: %s", fun, req, err)
		err = status.Error(codes.Internal, err.Error())
		return
	}

	res = &BindLoingidRes{
		Loginid: req.Loginid,
	}
	return
}

type LoginReq struct {
	LoginParam
	//登录校验码 可以是短信验证码、密码等跟当前登录类型关联的校验码
	LoginCode
} //@name 登录入参
type LoginResult struct {
	//登录认证信息
	AuthInfo *AuthInfo `json:"auth_info"`
	LoginID
	//用户信息
	UserInfo *UserInfo `json:"user_info"`
} //@name 登录结果

type LoginRes struct {
	LoginResult
} //@name 登录响应

func (m *UserServiceImpl) Login(ctx context.Context, req *LoginReq) (res *LoginRes, err error) {
	fun := "UserServiceImpl.Login -->"
	st := xtime.NewTimeStat()
	defer func() {
		xlog.Infow(ctx, fun, "durationMs", st.Millisecond(), "req", req, "res", res, "err", err)
	}()
	userId, err := m.getUserIdByLoginid(ctx, req.LoginID)
	if err != nil {
		xlog.Warnf(ctx, "%s get loginid info error req: %v err: %s", fun, req, err)
		err = status.Error(codes.Internal, err.Error())
		return
	}
	if userId <= 0 {
		xlog.Warnf(ctx, "%s not found loginid info error req: %v  ", fun, req)
		err = status.Error(codes.NotFound, "账号或密码错误")
		return
	}
	var userInfo *UserInfo
	userRes, err := m.GetUser(ctx, &GetUserReq{
		UserID: userId,
	})
	if err != nil {
		xlog.Warnf(ctx, "%s get user error: req: %v, err: %v", fun, req, err)
		err = status.Error(codes.NotFound, "账号或密码错误")
		return
	} else {
		userInfo = userRes.UserInfo
	}
	if userInfo == nil || userInfo.UserID <= 0 {
		xlog.Warnf(ctx, "%s not found user: req: %v ", fun, req)
		err = status.Error(codes.NotFound, "账号或密码错误")
		return
	}
	//校验验证码
	switch req.LoginCodeType {
	//账号密码
	case EnumLoginCodeType_Passwd:
		if hashValue(req.LoginCode.LoginCode) != userInfo.Passwd {
			xlog.Warnf(ctx, "%s not match  passwd: req: %v ", fun, req)
			err = status.Error(codes.NotFound, "账号或密码错误")
			return
		}
	}

	//执行登录
	authInfo, err := m.login(ctx, &LoginParam{
		LoginID: LoginID{
			LoginidType: req.LoginidType,
			Loginid:     req.Loginid,
			UserPoolID:  req.UserPoolID,
			Appid:       req.Appid,
		},
		UserId:     userId,
		ClientInfo: req.ClientInfo,
		AuthType:   req.AuthType,
	})

	res = &LoginRes{
		LoginResult: LoginResult{
			AuthInfo: authInfo,
			LoginID: LoginID{
				LoginidType: req.LoginidType,
				Loginid:     req.Loginid,
				UserPoolID:  req.UserPoolID,
				Appid:       req.Appid,
			},
			UserInfo: userInfo,
		},
	}
	return
}
